
/**
 * 处理对象
 * Java对象都是Object类的实例，都可直接调用该类中定义的方法，这些方法提供了处理Java对象的通用方法。
 */
class Person {
    private String name;

    public Person(String name) {
        this.name = name;
    }
}

class PersonTest {
    public static void printObject() {
        Person p = new Person("孙悟空");
        System.out.println(p);
        /*
         * 当使用该方法输出Person对象时，实际上输出的是Person对象的toString()方法的返回值。
         * 也就是说，上下这两行代码的效果完全一样。
         */
        System.out.println(p.toString());
        /*
         * toString()方法是Object类里的一个实例方法，所有的Java类都是Object类的子类，因此所有的Java对象都具有toString()方法。
         * 不仅如此，所有的Java对象都可以和字符串进行连接运算，
         * 当Java对象和字符串进行连接运算时，系统自动调用Java对象toString()方法的返回值和字符串进行连接运算
         */
        String pStr = p + "";
        /*
         * toString()方法是一个非常特殊的方法，它是一个“自我描述”方法，该方法通常用于实现这样一个功能：当程序员直接打印该对象时，
         * 系统将会输出该对象的“自我描述”信息，用以告诉外界该对象具有的状态信息。
         */
    }
}

/**
 * Object类提供的toString()方法总是返回该对象实现类的“类名+@+hashCode”值，这个返回值并不能真正实现“自我描述”的功能，
 * 因此如果用户需要自定义类能实现“自我描述”的功能，就必须重写Object类的toString()方法。
 * 例如下面程序。
 */
class Apple {
    private String color;
    private double weight;

    public Apple() {
    }

    public Apple(String color, double weight) {
        this.color = color;
        this.weight = weight;
    }

    /**
     * 重写toString方法，用于实现Apple对象的“自我描述”
     */
    public String toString() {
        return "一个苹果，颜色是：" + color + "，重量是：" + weight;
    }
}

class ToStringTest {
    public static void main() {
        Apple a = new Apple("红色", 5.68);
        System.out.println(a);
    }
}

/**
 * ==和equals方法
 * Java程序中测试两个变量是否相等有两种方式：一种是利用==运算符，另一种是利用equals()方法。
 * 当使用==来判断两个变量是否相等时，如果两个变量是基本类型变量，且都是数值类型（不一定要求数据类型严格相同），则只要两个变量的值相等，就将返回true。
 * <p>
 * 但对于两个引用类型变量，只有它们指向同一个对象时，==判断才会返回true。
 * ==不可用于比较类型上没有父子关系的两个对象。
 * 下面程序示范了使用==来判断两种类型变量是否相等的结果。
 */
class EqualTest {
    public static void main() {
        int it = 65;
        float fl = 65.0f;
        System.out.println("65和65.0f是否相等？" + (it == fl));   // true
        char ch = 'A';
        System.out.println("65和'A'是否相等？" + (it == ch));   // true
        String str1 = new String("hello");
        String str2 = new String("hello");
        System.out.println("str1和str2是否相等？" + (str1 == str2));   // false
        System.out.println("str1是否 equals str2？" + str1.equals(str2));    // true

        // 由于java.lang.String与EqualTest类没有继承关系
        // 所以下面语句导致编译错误
//        System.out.println("hello" == new EqualTest());

        /*
         * 对初学者而言，String还有一个非常容易迷惑的地方："hello"直接量和newString("hello")有什么区别呢？
         * 当Java程序直接使用形如"hello"的字符串直接量（包括可以在编译时就计算出来的字符串值）时，JVM将会使用常量池来管理这些字符串；
         * 当使用new String("hello")时，JVM会先使用常量池来管理"hello"直接量，再调用String类的构造器来创建一个新的String对象，
         * 新创建的String对象被保存在堆内存中。
         * 换句话说，new String("hello")一共产生了两个字符串对象。
         */
    }
}

/**
 * 下面程序示范了JVM使用常量池管理字符串直接量的情形。
 */
class StringCompareTest {
    public static void main() {
        String s1 = "学习Java";
        String s2 = "学习";
        String s3 = "Java";
        String s4 = "学习" + "Java";
        String s5 = "学" + "习" + "Java";
        String s6 = s2 + s3;
        String s7 = new String("学习Java");
        System.out.println(s1 == s4);   // true
        System.out.println(s1 == s5);   // true
        System.out.println(s1 == s6);   // false
        System.out.println(s1 == s7);   // false
        /*
         * JVM常量池保证相同的字符串直接量只有一个，不会产生多个副本。
         * 例子中的s1、s4、s5所引用的字符串可以在编译期就确定下来，因此它们都将引用常量池中的同一个字符串对象。
         *
         * 使用new String()创建的字符串对象是运行时创建出来的，它被保存在运行时内存区（即堆内存）内，不会放入常量池中。
         *
         * 但在很多时候，程序判断两个引用变量是否相等时，也希望有一种类似于“值相等”的判断规则，并不严格要求两个引用变量指向同一个对象。
         * 此时就可以利用String对象的equals()方法来进行判断，
         */
    }
}

/**
 * 下面程序示范了重写equals方法产生Person对象
 */
class Person2 {
    private String name;
    private String idStr;

    public Person2() {
    }

    public Person2(String name, String idStr) {
        this.name = name;
        this.idStr = idStr;
    }

    // 重写equals()方法，提供自定义的相等标准
    public boolean equals(Object obj) {
        // 如果两个对象为同一个对象
        if (this == obj) {
            return true;
        }
        if (obj != null && obj.getClass() == Person2.class) {
            Person2 p = (Person2) obj;
            // 并且当前对象的idStr与obj对象的idStr相等时才可以判断两个对象相等
            if (this.idStr.equals(p.idStr)) {
                return true;
            }
        }
        /*
         * 上面程序中判断obj是否为Person类的实例时，为何不用obj instanceof Person来判断呢？
         * 对于instanceof运算符而言，当前面对象是后面类的实例或其子类的实例时都将返回true，
         * 所以重写equals()方法判断两个对象是否为同一个类的实例时使用instanceof是有问题的。
         * 比如有一个Teacher类型的变量t，如果判断t instanceof Person，这也将返回true。
         * 但对于重写equals()方法的要求而言，通常要求两个对象是同一个类的实例，因此使用instanceof运算符不太合适。
         * 改为使用t.getClass()==Person.class比较合适。这行代码用到了反射基础，
         */
        return false;
    }
}

class OverrideEquals {
    public static void main() {
        Person2 p1 = new Person2("孙悟空", "123456789");
        Person2 p2 = new Person2("孙行者", "123456789");
        Person2 p3 = new Person2("孙悟饭", "999933231");

        System.out.println("p1和p2是否相等？" + p1.equals(p2));
        System.out.println("p2和p3是否相等？" + p2.equals(p3));
    }
}

/**
 * 通常而言，正确地重写equals()方法应该满足下列条件。
 * ➢ 自反性：对任意x, x.equals(x)一定返回true。
 * ➢ 对称性：对任意x和y, 如果y.equals(x)返回true，则x.equals(y)也返回true。
 * ➢ 传递性：对任意x, y, z, 如果x.equals(y)返回ture，y.equals(z)返回true，则x.equals(z)一定返回true。
 * ➢ 一致性：对任意x和y, 如果对象中用于等价比较的信息没有改变, 那么无论调用x.equals(y)多少次，返回的结果应该保持一致，要么一直是true，要么一直是false。
 * ➢ 对任何不是null的x, x.equals(null)一定返回false。
 * <p>
 * Object默认提供的equals()只是比较对象的地址，即Object类的equals()方法比较的结果与==运算符比较的结果完全相同。
 * 因此，在实际应用中常常需要重写equals()方法，重写equals方法时，相等条件是由业务要求决定的，因此equals()方法的实现也是由业务要求决定的。
 */

public class Test {
    public static void main(String[] args) {
        PersonTest.printObject();
        ToStringTest.main();
        EqualTest.main();
        StringCompareTest.main();
        OverrideEquals.main();
    }
}
